<html>
<head>
<meta http-equiv="refresh" content="600;URL=ref_fuzz5.html">
</head>
<body onload="run_tests()">

<font face="Bitstream Vera Sans Mono,Andale Mono,Lucida Consol" size=-1>
<b><font size=+2> ref fuzz v5</font></b> by <a href="mailto:lcamtuf@gmail.com">lcamtuf@gmail.com</a>
<hr size=1 color=red>
<p>

<script>

var ODDS = 10;				/* Toy with, on average, every ODDS-th property or method.   */
var REF_ODDS = 10;       	/* Save, on average, every REF_ODDS-th reference.             */
var RESET_ODDS = 10;		/* Reset parser state, on average, every RESET_ODDS turns.    */
var PASSES = 20;			/* Recursively crawl DOM in up to that many passes.           */
var MEGS = 10;				/* Heap spray every now and then with MEGS megabytes of junk. */
var PARAMS = 10;			/* Number of made up parameters to pass to a function call.   */
var MAX_REFS = 1000;

/* List of elements to create, then crawl and mess with their properties. */

var tag_list = [
  "CANVAS",
  "STYLE",
  "SCRIPT",
  "EMBED",
  "OBJECT",
  "IFRAME",
  "TEXTAREA",
  "INPUT",
  "FORM",
  "A",
  "BODY",
  "HTML",
  "DIV",
  "TABLE",
  "AREA",
  "TD",
  "TR",
  "LINK",
  "BASE",
  "FONT",
  "HEAD",
  "IMG",
  "MAP",
  "META",
  "OL",
  "LI",
  "TBODY",
  "TITLE",
  "H1",
  "BLINK",
  "!DOCTYPE",
  "AREA",
  "COL",
  "SPAN",
  "FRAMESET",
  "FRAME",
  "UL",
  "OPTION",
  "NOSCRIPT",
  "NOFRAMES",
  "PARAM",
  "TFOOT",
  "XMP",
  "ISINDEX",
  "SELECT",
  "CENTER",
  "FIELDSET",
  "HR",
  "LABEL",
  "OPTGROUP",
  "P",
  "BR",
  "IMG"
];

/* Properties and methods we should stay away from to prevent clobbering the document,
   or avoid known crashes. */

var property_blacklist = {
  "parentNode" : 1,
  "nextSibling" : 1,
  "previousSibling" : 1,
  "top" : 1,
  "parent" : 1,
  "opener" : 1,
  "ownerDocument" : 1,
  "parentElement" : 1,
  "parentTextEdit" : 1,
  "offsetParent" : 1,
  "DOCUMENT_POSITION_PRECEDING" : 1,
  "document" : 1,
  "activeElement" : 1,
  "documentElement" : 1,
  "onbeforeunload" : 1,
  "alert" : 1,
};


var log_box;			/* Log output <textarea>                         */
var cur_obj;			/* Reference to currently tested object          */
var out_div;			/* Top-level sandbox in which cur_obj is created */
var stat_box;			/* Stats <span>					 */

var cur_tag;			/* Name of cur_obj tag                           */
var state = 0;			/* Fuzzer state machine selector (0-3)           */
var round = 0;			/* Fuzzer round counter                          */

var stored_refs;		/* Stored reference objects			 */
var stored_refs_origin;		/* Origin descriptors for reference objects      */
var stored_refs_round;		/* Reference creation time (round number)        */

var message_data = '';		/* Message log for debugging purposes            */

var spray_str;

/* Try to spray the memory with known strings to make fun crashes more visible */

function heap_spray() {

  if (MEGS == 0 || R(2) == 0) return;

  if (!spray_str) {
    var spray_str = "ABCDABCD";
    for (var i=0;i<21;i++) spray_str += spray_str; /* 16M */
  }

  window.name = Math.random() + spray_str + Math.random();

  eval("window.name=''");

}


/* Prepare for fuzzing. */

function run_tests() {

  heap_spray();

  out_div  = document.getElementById('sandbox');
  log_box  = document.getElementById('results');
  stat_box = document.getElementById('stats');
  out_div.innerHTML = '';

  document.getElementById('start').disabled = true;

  setInterval('try { next_step() } catch (e) { location.reload(); }',1);

}


/* Log an event. */

function log(message) {

  if (document.body.innerHTML.indexOf('ref f' + 'uzz v') == -1) {

    alert('Document state compromised!');
    state = 999;
    throw 1;

  }

  // Logging slows the process down very significantly.
  // Use browser-specific console output instead, perhaps.

  //message_data += '* ' + message + '\n';
  //log_box.value = message_data.substr(message_data.length - 2048);
  //log_box.scrollTop = log_box.scrollHeight;

}


/* Generate a random integer (0 ... mod-1) */

function R(mod) {
  return Math.floor(Math.random() * mod);
}


/* Possibly store a new reference (string, object, etc). */

function add_ref(obj, origin) {

  if (stored_refs.length > MAX_REFS || R(REF_ODDS) != 0) return;

  /* Be more conservative about adding new strings. */
  if (typeof obj == 'string' && R(ODDS * ODDS) != 0) return;

  for (var i=0;i<stored_refs.length;i++)
    if (stored_refs[i] === obj)
      return;

  log('-> Adding reference ' + obj + ' (origin: ' + origin + ', round: ' + round + '): ref#' + stored_refs.length);

  stored_refs.push(obj);
  stored_refs_origin.push(origin);
  stored_refs_round.push(round);

}


/* Scan object properties, store references found. */

function scan_properties(object, origin) {

  for (name in object) {

    var e_value = null;

    if (name == '0' || property_blacklist[name] == 1 || R(ODDS) != 0) continue;

    log('Trying to read ' + name + ' property...');

    try { 
      e_value = eval('object.' + name); 
      log('...result is "' + e_value + '" (' + typeof e_value + ')');
    } catch (e) { 
      log('...received exception (' + e + ')');
    }

    if (e_value != null && typeof e_value != 'function' && property_blacklist[name] != 1) 
      add_ref(e_value, origin + '.' + name); 

  }

}


/* Scan and call object functions, store references found in return values. */

function call_functions(object, origin) {

  for (name in object) {
  
    if (name == '0' || property_blacklist[name] == 1 || R(ODDS) != 0) continue;

    var e_value = null;

    log('Trying to read ' + name + '...');

    try { 
      e_value = eval('object.' + name);
      log('...result is "' + e_value + '" (' + typeof e_value + ')');
    } catch (e) {
      log('...received exception (' + e + ')');
    }

    if (typeof e_value == 'function') {

      e_value = null;

      var par_str = "";
      var desc_str = ""

      try {

        for (var i=0;i<PARAMS;i++) {
          var rnd = R(stored_refs.length);
          if (i > 0) { par_str += ", "; desc_str += ", "; }
          par_str += 'stored_refs[' + rnd + ']'
          desc_str += '[ref#' + rnd + '/org:' + stored_refs_origin[rnd] + '/round:' + stored_refs_round[rnd] + ']';
        }

        log('Trying to call ' + name + '(' + desc_str + ')...');
 
        e_value = eval('object.' + name + '(' + par_str + ')');

        log('...result is "' + e_value + '" (' + typeof e_value + ')');

      } catch (e) { 
        log('...received exception (' + e + ')');
      }

      if (e_value != null) 
        add_ref(e_value, '(' + origin + '.' + name + '(' + desc_str + '))');

    }

  }

}


/* Try to mess with the state of an object. */

function tweak_properties(object, origin) {

  for (name in object) {

    if (name == '0' || property_blacklist[name] == 1 || R(ODDS) != 0) continue;

    var e_value = null;

    log('Trying to read ' + name + '...');

    try { 
      e_value = eval('object.' + name);
      log('...result is "' + e_value + '" (' + typeof e_value + ')');
    } catch (e) {
      log('...received exception (' + e + ')');
    }

    /* Bail out on functions, but do not give up on exceptions. */
    if (typeof e_value == 'function') continue;

    if (R(ODDS) == 0) {

      log('Trying to delete property ' + name + '...');

      try { 
        eval('delete object.' + name);
        log('...success.');
      } catch (e) { 
        log('...received exception (' + e + ')');
      }

    } else {

      var rnd = R(stored_refs.length);

      log('Trying to set property ' + name + ' to ref#' + rnd + '/org:' + 
          stored_refs_origin[rnd] + '/round:' + stored_refs_round[rnd] + '...');

      try { 
        eval('object.' + name + ' = stored_refs[rnd]');
        log('...success.');
      } catch (e) { 
        log('...received exception (' + e + ')');
      }
 
    }

  }

}


/* Main fuzzer state machine */

function next_step() {

  round++;

  switch (state) {

    case 0: 

      /*********************************************/
      /* PHASE 0: Create new HTML tag in a sandbox */
      /*********************************************/

      if (stored_refs == undefined || R(RESET_ODDS) == 0) {

        /* Various interesting values to begin with. */

        stored_refs        = [ 0, 1, 1e6, -1e6, 1e-6, 1e100, null, undefined, 'pink', navigator, Infinity, false, true, eval ];
        stored_refs_origin = [ 1, 2,   3,    4,     5,     6,    7,         8,      9,        10,       11,    12,   13,   14 ];
        stored_refs_round  = [ 0, 0,   0,    0,     0,     0,    0,         0,      0,         0,        0,     0,    0,    0 ];

        message_data = '';

      } else {

        log('Opting to continue without resetting state.');

      }


      log('+++ Phase 0: initialization [round: ' + round + '] +++');

      try { 
        if (R(2) == 0) out_div.removeChild(cur_obj); 
        log('Previous object removed OK.');
      } catch (e) {
        log('Failed to remove previously used object.');
      }

      cur_tag = tag_list[R(tag_list.length)];

      log('Picked target tag: <' + cur_tag + '>');

      cur_obj = document.createElement(cur_tag);
      out_div.appendChild(cur_obj);

      try { 
        cur_obj.innerHTML = 'Hello world'; 
        log('Populated object with sample HTML.');
      } catch (e) {
        log('Unable to write to object innerHTML.');
      }

      try { 
        cur_obj.value = 'Hello world'; 
        log('Populated object with sample value.');
      } catch (e) {
        log('Unable to write to object value.');
      }

      break;

    case 1: 

      /*****************************************************/
      /* PHASE 1: Crawl references, randomize object state */
      /*****************************************************/

      /* (fall through) */

    case 3:

      /***********************************/
      /* PHASE 3: Poke stale references */
      /**********************************/

      if (state == 1)
        log('+++ Phase 1: crawl and tweak [round: ' + round + '] +++');
      else
        log('+++ Phase 3: crawl and tweak over stale references [round: ' + round + '] +++');

      /* Collect variable values */

      if (cur_obj) {
        log('Stage 1: collecting object property values.');
        scan_properties(cur_obj, '<' + cur_tag + '>');
      } else {
        log('Stage 1 suppressed (cur_obj deleted)');
      }

      /* Call functions and collect return values */

      if (cur_obj) {
        log('Stage 2: calling object methods, collecting return values.');
        call_functions(cur_obj, '<' + cur_tag + '>');
      } else {
        log('Stage 2 suppressed (cur_obj deleted)');
      }

      /* On all collected objects, seek & call nested functions. Several
         passes over newly discovered references are used. */

      var ref_start = 0;

      for (var pass=0;pass<PASSES;pass++) {

        var ref_max = stored_refs.length;

        log('Stage 3.' + pass + ': collecting properties, calling functions on references.');

        for (;ref_start < ref_max; ref_start++) {

          if (typeof stored_refs[ref_start] == 'object' && R(ODDS) == 0) {
            log('Probing ref#' + ref_start + ': ' + stored_refs_origin[ref_start] + ' (from round: ' + stored_refs_round[ref_start] + ')');
            scan_properties(stored_refs[ref_start], stored_refs_origin[ref_start]);
            call_functions(stored_refs[ref_start], stored_refs_origin[ref_start]);
          }

        }

      }

      /* Now, randomly tweak parameters of all collected objects. */

      if (cur_obj) {
        log('Stage 4: tweaking object properties and functions.');
        tweak_properties(cur_obj, '<' + cur_tag + '>');
      } else {
        log('Stage 4 suppressed (cur_obj deleted)');
      }

      log('Stage 5: tweaking properties and functions on references.');

      for (var i=0;i<stored_refs.length;i++)
        if (typeof stored_refs[i] == 'object' && R(ODDS) == 0) {
          log('Tweaking ref#' + i + ': ' + stored_refs_origin[i] + ' (from round: ' + stored_refs_round[i] + ')');
          tweak_properties(stored_refs[i], stored_refs_origin[i]);
        }

      break;

    case 2:

      /*************************************************************/
      /* PHASE 2: Discard object, try to force garbage collection. */
      /*************************************************************/

      log('+++ Phase 2: discarding object [round: ' + round + ']+++');

      heap_spray();

      switch (R(4)) {

        case 0:
          log('Trying to overwrite innerHTML...');
          try {
            out_div.innerHTML = '';
            log('...success.');
          } catch (e) {
            log('...received exception (' + e + ')');
          }
          break;

        case 1:
          log('Trying to delete object...');
          try {
            delete cur_obj;
            log('...success.');
          } catch (e) {
            log('...received exception (' + e + ')');
          }
          break;

        case 2:
          log('Trying to delete child node via DOM...');
          try {
            out_div.removeChild(cur_obj);
            log('...success.');
          } catch (e) {
            log('...received exception (' + e + ')');
          }
          break;

        case 3:
          log('No action taken in this round.');
          break;

      }

      heap_spray();

      break;

    case 999:
      return;

  }

  stat_box.innerHTML = 'round ' + round + ', ' + stored_refs.length + ' refs, state ' + state;

  state = (state + 1) % 4;

}

</script>

<input type=submit onclick="run_tests()" value="Start testing" id=start>

<p>

Log data:<br>
<textarea rows=30 readonly id=results style="border: 1px solid teal; width: 90%; font-family: 'Bitstream Vera Sans Mono','Andale Mono','Lucida Console'; font-size: 0.8em">
</textarea>
<br>
Stats: <span id=stats></span>

<p>
Testing sandbox:<br>
<div id=sandbox style="border: 1px solid red; width: 20%; height: 10%">
</div>

